package jmpo.mth;

/**
 * Потоки не всегда работают независимо друг от друга.
 * Иногда потокам требуется кооперация с использованием 
 * общего ресурса, например памяти. Важно уметь управлять
 * доступом к общему ресурсу.
 * 
 * В Java есть масса методов для <<синхронизации>> доступа
 * к ресурсам:
 *     Semaphore - cемафоры Дейкстры
 *     Мониторы Хоара (synchronized и condition variables -> obj.wait() obj.notify())
 *     BlockingQueue<T> - блокирующая очередь сообщений
 *     
 *     CountDownLatch - останавливает потоки до тех пор, пока счётчик не доходит до нуля
 *     CyclicBarrier  - несколько потоков жду у некой точки
 *     Phaser         - переиспользуемые барьер типа CyclicBarrier
 *     Exchanger<T>   - два потока могут обменяться значениями в точке
 *     
 *     ReentrantLock           - блокирующий замок
 *     ReentrantReadWriteLock  - отдельные блоки на чтение и запись; несколько замков в одном
 */

/**
 * Реализация Producer-Consumer с использованием монитора Хоара.
 */
final class ProducerConsumerMonitorExample {
    private static final class LongWrapper {
        public long get() { return this.l; }
        public void set(long l) { this.l = l; }
        private volatile long l; 
    }

    private ProducerConsumerMonitorExample() {
        this.empty = true;
        this.data = new LongWrapper();
    }

    private volatile boolean empty;
    private final LongWrapper data;

    private synchronized void push(long l) throws InterruptedException {
        while(!empty) this.wait();
        data.set(l);
        empty = false;
        this.notify();
    }

    private synchronized long get() throws InterruptedException {
        while(empty) this.wait();
        long l = data.get();
        empty = true;
        this.notify();
        return l;
    }

    public static void test() {
        final var monitor = new ProducerConsumerMonitorExample();

        Thread producer = new Thread(() -> {
            try {
                for(long cnt = 0; cnt != 10; ++cnt) {
                    monitor.push(cnt);
                    System.out.format("Produced %d\n", cnt);
                }
            } catch (InterruptedException e) {
                throw new RuntimeException("Unexpected interruption", e);
            }
        });

        Thread consumer = new Thread(() -> {
            try {
                for(long cnt = 0; cnt != 10; ++cnt) {
                    long l = monitor.get();
                    System.out.format("Consume %d\n", l);
                }
            } catch (InterruptedException e) {
                throw new RuntimeException("Unexpected interruption", e);
            }
        });

        producer.start();
        consumer.start();
    }
}

/**
 * В Java нет специального объекта mutex. Если необходимо организовать
 * mutual exclusion, то следует применять Java мониторы.
 */
final class CriticalSectionExample {
    private static volatile long l = 0l;
    private static CriticalSectionExample monitor = new CriticalSectionExample();
    
    private static void synchCall() {
        /**
         * монитором может быть любой объект
         */
        synchronized (monitor) {
            ++l;
        }
    }

    private static void nonsynchCall() {
        ++l;
    }

    public static void testNosynch() {
        Thread t1 = new Thread(() -> {
            for (long cnt = 0l; cnt != 10000; ++cnt)
                CriticalSectionExample.nonsynchCall();
        });

        Thread t2 = new Thread(() -> {
            for (long cnt = 0l; cnt != 10000; ++cnt)
                CriticalSectionExample.nonsynchCall();
        });
        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            System.out.println(e.getMessage());
            System.exit(1);
        }

        System.out.println(l);
    }

    public static void testSynch() {
        Thread t1 = new Thread(() -> {
            for (long cnt = 0l; cnt != 10000; ++cnt)
                CriticalSectionExample.synchCall();
        });

        Thread t2 = new Thread(() -> {
            for (long cnt = 0l; cnt != 10000; ++cnt)
                CriticalSectionExample.synchCall();
        });
        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            System.out.println(e.getMessage());
            System.exit(1);
        }

        System.out.println(l);
    }
}

public class ThreadSynchronization {
    public static void main(String[] args) {
        if (false) ProducerConsumerMonitorExample.test();
        if (false) CriticalSectionExample.testNosynch();
        if (false) CriticalSectionExample.testSynch();
    }
}
